/******************************************************************************
 * \brief	通信中的链路层处理（网络套接字）
 * \details	用来模拟两个嵌入式设备间的串口通信；有点区别的是套接字是可靠通信，串口是
 *			不可靠通信，套接字可以不使用校验，串口必须要使用校验
 * \note	File format: UTF-8，中文编码：UTF-8
 * \author	将狼才鲸
 * \date	2023-03-25
 ******************************************************************************/

/*********************************** 头文件 ***********************************/
#include "scp_dev_socket.h"
#include "dev_socket.h"
#include "util_comm.h"

/********************************** 类型定义 **********************************/
/**
 * \brief	和本设备（套接字）相关的通信结构体
 * \note	整个结构体大小不要超过scp_dev_t中priv_data的长度32字节
 */
typedef struct _scpdev_sockt {
	uint32_t magic;	/* 魔术字，本设备模块的包头识别，便于流式设备一个字节一个字节的读数据 */
	segment_t *last_rxseg;	/* 已经申请了空间的消息句柄，如果已成功接收，里面则会存入有效数据 */
	uint32_t frame_id;		/* 本设备数据帧id，发送时进行递增 */
	struct list_head reqs_list;	/* 服务器端收到的请求包队列 */
	struct list_head resp_list;	/* 客户端收到的响应包队列 */
} scpdev_socket_t;

/********************************** 私有函数 **********************************/
/**
 * \brief	清空设备缓存
 */
static int scpdev_socket_reset(scpdev_socket_t *ds)
{
	irq_flag lock;
	segment_t *seg = NULL;

	irq_disable(lock);

	/* 处理响应队列 */
	while (!list_empty_careful(&ds->resp_list)) {	/* 如果响应队列非空 */
		seg = list_first_entry(&ds->resp_list, segment_t, node);	/* 弹出第一个数据但不删除 */
		list_del_init(&seg->node);	/* 从队列中删除弹出的数据 */
		if (!ds->last_rxseg)
			ds->last_rxseg = seg;	/* 前一个要响应的包 */
		else
			scpseg_free(seg);		/* 剩下的包全部删除 */
	}

	while (!list_empty_careful(&ds->reqs_list)) {	/* 如果请求队列非空 */
		seg = list_first_entry(&ds->reqs_list, segment_t, node);
		list_del_init(&(seg->node));
		if (ds->last_rxseg == NULL)
			ds->last_rxseg = seg;
		else
			scpseg_free(seg);
	}

	ds->magic = 0;		/* 去掉设别包头的魔术字 */
	ds->frame_id = 0;	/* 数据帧ID从头开始计数 */

	/* 重新初始化请求和响应队列 */
	init_list_head(&(ds->reqs_list));
	init_list_head(&(ds->resp_list));

	irq_enable(lock);

	return 0;
}

/**
 * \brief	对发送消息进行发送前的准备
 * \details	设置段ID和魔术字
 * \param	seg:	数据段句柄
 * \param	wmode:	发送消息的阶段，RWMODE_START...
 */
static int scpdev_socket_send_prepare(segment_t *seg, uint8_t wmode)
{
	uint16_t pid;
	uint8_t *buf;

	seg_pkt_head_t *pkt;	/* 段数据头 */
	scpdev_socket_t *ds;	/* 网络设备句柄 */
	scp_dev_t *pdev;		/* 设备句柄 */

	pdev = seg->pdev;
	ds = (scpdev_socket_t *)scpdev_pri(pdev);
	pkt = (seg_pkt_head_t *)scpseg_data(seg);

	pid = ntohs(pkt->PID);	/* 协议ID，如SCP_PG_DEBUG_PUTS... */

	/* 如果段ID未设置、协议ID是响应类消息、协议ID是消息响应 */
	if ( (0 == pkt->SID) || (pid & SCP_RPID_MARK) || (pid == SCP_PID_ACK) ) {
		do {
			pkt->SID = 0xFF & ds->frame_id++;	/* 设置新的递增段ID，SID不能为0 */
		} while (!pkt->SID);
		pkt->SID = htonl(pkt->SID);
	}
	pkt->CRC = 0;

	buf = ((uint8_t *)scpseg_data(seg)) - 4;	/* 获取seg_pkt_head_t前面的一个数 */
	*((uint32_t *)buf) = htonl(SCP_MAGIC);		/* 写入魔术字 */

	return 0;
}

/**
 * \brief	每次收到一点数据都进来检查，返回收满或收了部分的消息句柄，或者申请新的消息句柄
 * \details	得到要接着继续接收数据的消息句柄，或者还没开始收数据的消息句柄；
 *			如果前一个收到的包还未被处理，则先将其压到队列里去，然后重新申请一个消息空间待用
 *			（待真正接收后填入数据）；
 * \return	返回已收满，或收了一部分，或成功重新申请到的消息句柄，或者申请失败返回NULL
 */
static segment_t *scpdev_socket_check_get_new(scp_dev_t *pdev)
{
	scpdev_socket_t *ds = (scpdev_socket_t *)scpdev_pri(pdev);
	seg_pkt_head_t *pkt;
	segment_t *last_rxseg = ds->last_rxseg;	/* 前一个申请过的消息句柄 */

	/* 判断是否上一次接收的包还未处理；
	   如果前一个包已经接收并填充了数据，则处理完后ds->last_rxseg指针应该为空，
	   如果不为空说明里面的数据还未处理，则把它压到队列里去 */
	if (last_rxseg && ds->magic == SCP_MAGIC) {
		/* 如果已存在前一个未处理的接收包 */
		pkt = (seg_pkt_head_t *)scpseg_data(last_rxseg);	/* 获取包头数据 */
		if (last_rxseg->LEN >= sizeof(seg_pkt_head_t) + ntohl(pkt->LEN)) {
			/* 如果已收到需要的包长度，则包结束 */
			uint16_t pid = ntohs(pkt->PID);		/* 应用层协议ID，固定值 */
			init_list_head(&last_rxseg->node);	/* 初始化这个包的节点 */
			if ((pid & SCP_RPID_MARK) || (pid == SCP_PID_ACK)) {
				/* 如果是客户端收到的响应包 */
				list_add_tail(&last_rxseg->node, &ds->resp_list);	/* 将这个收到的包加入到响应队列里去 */
			} else {
				/* 如果是服务器端收到的请求包 */
				list_add_tail(&last_rxseg->node, &ds->reqs_list);	/* 加入请求队列 */
			}
			last_rxseg = NULL;				/* 用于后面重新申请空间 */
			ds->last_rxseg = last_rxseg;	/* 处理完成，置空 */
			ds->magic = 0;
		}
		/* 如果未收完数据，则返回该收了部分数据的消息句柄 */
	}

	if (!last_rxseg) {
		/* 如果已收满，则重新申请消息（数据段）的内存空间 */
		last_rxseg = scpseg_new(pdev);	/* 申请数据段句柄空间 */
		if (last_rxseg) {
			last_rxseg->LEN = 0;
			ds->last_rxseg = last_rxseg;
			ds->magic = 0;
		}
	}

	return last_rxseg;
}

/**
 * \brief	从设备中接收对方发来数据包，收到的包放入接收队列中
 * \details	可以未收到或只接收一半，之后的轮询可以中途进入继续接收；
 *			当前是用socket模拟的串口处理，接收数据时一个字符一个字符的读；
 *			服务器端或客户端都从这里接收数据，所以有可能收到的是请求包，
 *			也可能是响应包
 * \param	pdev:	设备句柄
 * \return	返回本次读到的字节数，可以是0表示未读到数据
 */
static int scpdev_socket_receive(scp_dev_t *pdev)
{
	scpdev_socket_t *ds = (scpdev_socket_t *)scpdev_pri(pdev);
	int ret = 0, chr = 0;			/* 当前按字节读的结果 */
	segment_t *last_rxseg = NULL;	/* 要接着继续接收数据的消息句柄，或者还没开始收数据的消息句柄 */
	uint32_t magic;
	uint8_t *dat;
	int res;

	/* 得到要接着继续接收数据的消息句柄，或者还没开始收数据的消息句柄；
	   如果前一个包完整接收了但还未处理，则先将其压到队列里去，然后重新申请一个新的消息空间 */
	last_rxseg = scpdev_socket_check_get_new(pdev);
	if (!last_rxseg)
		return -ENOMEM;	/* 收完一个包后重启申请空间时失败 */

	/* 定位到上次已接收到的位置或起始位置，继续接收或开始接收 */
	dat = ((uint8_t *)scpseg_data(last_rxseg)) + last_rxseg->LEN;

	//TODO: 如果链路不是串口，其实可以将一个字节一个字节的读改为一大块一大块的读来提高效率
	/* 接收数据；一次性将接收缓存里面的数据读完，不会保证当前这一次执行就读到一个完整的包 */
#ifdef CONFIG_IS_SERVER
	res = socket_server_recv((char *)&chr, sizeof(char));	/* 读一个字节 */
#else
	res = socket_client_recv((char *)&chr, sizeof(char));
#endif
	if (res > 0)
		pr_dbg("\nrecv: "); fflush(stdout);
	while (res > 0) {
		/* 处理收到的一个字节 */
		magic = ds->magic;	/* 刚开始要接收的空起始码，或者已经接收了一部分的起始码 */
		if (magic == SCP_MAGIC) {
			/* 如果已收到魔术字（起始码），那么当前的都是数据 */
			*dat++ = chr;
			last_rxseg->LEN++;
			ret++;
			if (last_rxseg->LEN > sizeof(seg_pkt_head_t)) {
				pr_dbg("%c", chr); fflush(stdout);
			} else {
				pr_dbg("%02x", chr); fflush(stdout);
			}
		} else {
			/* 如果没收到正确的魔术字，则将读到的所有数据循环判断魔术字，直到识别成功为止；
			   这样也可以消除一部分两个包之间的脏数据 */
			magic = (magic << 8) | chr;
			ds->magic = magic;
			last_rxseg->LEN = 0;
			pr_dbg("%02x", chr); fflush(stdout);
		}

		last_rxseg = scpdev_socket_check_get_new(pdev);	/* 如果前一个包完整接收了，则将其压到队列里去，然后重新拿一个空的包空间备用 */
		if (!last_rxseg) {
			return -ENOMEM;	/* 收完一个包后重启申请空间时失败 */
		}

#ifdef CONFIG_IS_SERVER
		res = socket_server_recv((char *)&chr, sizeof(char));	/* 读一个字节 */
#else
		res = socket_client_recv((char *)&chr, sizeof(char));
#endif
	};

	return ret;	/* 返回本次读到的长度，或者0，或者错误码 */
}

/********************************** 接口函数 **********************************/
/**
 * \brief	初始化网络套接字进程间通信链路
 * \param	hwaddr:	如果是多核通信，可以使用内存地址直接传递数据
 * \param	type:	指定通信设备，如串口、网络、核间通信、进程间通信等
 */
scp_dev_t *scpdev_socket_init(uint32_t hwaddr, int devtype)
{
	irq_flag lock;
	scpdev_socket_t *ds;
	scp_dev_t *pdev;

	/* 需是网络套接字 */
	if (devtype != SCP_DEVTYPE_SOCKET)
		return NULL;
	
	/* 分配通信结构体空间 */
	pdev = scpdev_new(sizeof(scpdev_socket_t));
	if (!pdev)
		return NULL;

	/* 从scp_dev_t的priv_data获取scpdev_socket_t地址 */
	ds = (scpdev_socket_t *)scpdev_pri(pdev);

	pdev->dev_type = devtype;	/* 通信设备类型 */
	pdev->hwaddr = hwaddr;		/* 直接传递内存地址给对方，只适用于多核的情况 */
	/* 支持的最大数据长度 */
	pdev->max_size = DATBUF_MAX_BUF_LEN - sizeof(segment_t);

	irq_disable(lock);
	/* 初始化收发消息队列 */
	init_list_head(&(ds->reqs_list));
	init_list_head(&(ds->resp_list));
	irq_enable(lock);

#ifdef CONFIG_IS_SERVER
	socket_server_init();	/* 服务器端套接字初始化 */
#else
	socket_client_init();	
#endif

	return pdev;
}

/**
 * \brief	设备销毁
 */
int scpdev_socket_exit(scp_dev_t *pdev)
{
	scpdev_socket_t *ds = NULL;

	if (!scpdev_valid(pdev, SCP_DEVTYPE_SOCKET))
		return -EACCES;

	ds = (scpdev_socket_t *)scpdev_pri(pdev);
	scpdev_socket_reset(ds);

	scpdev_free(pdev);

#ifdef CONFIG_IS_SERVER
	socket_server_exit();
#else
	socket_client_exit();	
#endif

	return 0;
}

/**
 * \brief	准备好设备，清空设备缓存
 */
int scpdev_socket_start(scp_dev_t *pdev)
{
	scpdev_socket_t *ds;

	/* 设备结构体的设备类型正确 */
	if (!scpdev_valid(pdev, SCP_DEVTYPE_SOCKET))
		return -EACCES;

	/* 从scp_dev_t中获取scpdev_socket_t空间 */
	ds = (scpdev_socket_t *)scpdev_pri(pdev);

	/* 清空设备缓存 */
	scpdev_socket_reset(ds);
	
	return 0;
}

/**
 * \brief	释放设备缓存
 */
int scpdev_socket_stop(scp_dev_t *pdev)
{
	scpdev_socket_t *ds;
	if (!scpdev_valid(pdev, SCP_DEVTYPE_SOCKET))
		return -EACCES;
	
	ds = (scpdev_socket_t *)scpdev_pri(pdev);
	scpdev_socket_reset(ds);	/* 释放设备缓存 */

	if (ds->last_rxseg) {
		scpseg_free(ds->last_rxseg);
		ds->last_rxseg = NULL;
	}
	
	return 0;
}

/**
 * \brief	设备发送数据
 * \param	seg:	段句柄
 * \param	wmode:	单次消息发送的某个阶段：RWMODE_START、RWMODE_FINISH、RWMODE_REQUEST、RWMODE_RESPOND
 */
int scpdev_socket_send(segment_t *seg, int wmode)
{
	int res = 0;
	irq_flag lock;
	scp_dev_t *pdev;		/* 设备句柄 */
	scpdev_socket_t *ds;	/* 网络设备句柄 */
	
	if ( !scpseg_valid(seg)
	     || !scpdev_valid(seg->pdev, SCP_DEVTYPE_SOCKET))
		return -EACCES;

	pdev = seg->pdev;
	ds = (scpdev_socket_t *)scpdev_pri(pdev);

	switch(wmode)
	{
	case RWMODE_RESPOND:	/* 响应包的处理 */
		scpdev_socket_send_prepare(seg, RWMODE_RESPOND);	/* 对要发送的消息进行预处理，填入一点默认值 */
	case RWMODE_REQUEST:	/* 请求包的处理 */
	{
		/* 直接将已处理好的包发送出去 */
		seg_pkt_head_t *pkt = (seg_pkt_head_t *)scpseg_data(seg);
		uint8_t *buf = ((uint8_t *)scpseg_data(seg)) - 4;	/* 前面加上了4字节魔术字内容 */
		res = (int)(ntohl(pkt->LEN) + sizeof(seg_pkt_head_t) + 4);
#ifdef CONFIG_IS_SERVER
		res = socket_server_send(buf, res);	/* 从链路上发送指定长度的数据 */
#else
		res = socket_client_send(buf, res);
#endif
	} break;
	case RWMODE_START:		/* 开始消息发送前的准备 */
	case RWMODE_FINISH:		/* 消息处理完后进行一些清理 */
	{
		segment_t *s;
		irq_disable(lock);		
		while (!list_empty_careful(&ds->resp_list)) {	/* 如果响应队列有消息 */
			s = list_first_entry(&(ds->resp_list), segment_t, node);	/* 弹出一个响应消息 */
			list_del_init(&s->node);	/* 从队列中删除该响应消息 */
			if (!ds->last_rxseg)
				ds->last_rxseg = s;		/* 留一个要响应的消息 */
			else
				scpseg_free(s);			/* 其它的消息全部删掉 */
		}		
		init_list_head(&ds->resp_list);	/* 清除响应消息队列 */
		irq_enable(lock);

		if (RWMODE_START == wmode) {
			res = 0;
			while (seg && !res) {	/* 处理一个消息或循环处理一个消息系列，当前只需要操作单个消息 */
				s = seg;
				seg = seg->next;
				res = scpdev_socket_send_prepare(s, RWMODE_REQUEST);	/* 对要发送的消息进行预处理，填入一点默认值 */
			}
		}
	} break;
	};

	return res;
}

/**
 * \brief	设备接收数据包；有接收队列，从中获取最早到达的包
 * \details	轮询的读消息，一次轮询不一定读到完整的包，可能多次轮询后才能收满包
 * \param	pdev:	设备句柄
 * \param	rmode:	读数据时的步骤：RWMODE_REQUEST...
 * \return	已收完则返回数据包，或者未收完或未收到返回NULL
 */
segment_t *scpdev_socket_read(scp_dev_t *pdev, int rmode)
{
	irq_flag lock;
	segment_t *seg = NULL;
	scpdev_socket_t *ds = NULL;

	if (!scpdev_valid(pdev, SCP_DEVTYPE_SOCKET) )
		return NULL;

	ds = (scpdev_socket_t *)scpdev_pri(pdev);

	/* 从设备中接收数据包，有可能成功接收，也有可能未接收到；
	   收到了包之后会放到客户端或者服务器端相应的接收队列中 */
	scpdev_socket_receive(pdev);

	/* 从接收队列中获取之前读到的包 */
	irq_disable(lock);
	if (rmode == RWMODE_REQUEST) {
		/* 如果是服务器端 */
		if (!list_empty_careful(&ds->reqs_list)) {
			/* 如果响应队列非空 */
			seg = list_first_entry(&ds->reqs_list, segment_t, node);	/* 从接收队列中读出完整的一个响应包 */
			list_del_init(&seg->node);	/* 读完后删除接收队列的节点 */
		}
	} else {
		/* 如果是客户端 */
		if (!list_empty_careful(&ds->resp_list)) {
			seg = list_first_entry(&ds->resp_list, segment_t, node);
			list_del_init(&seg->node);
		}
	}
	irq_enable(lock);

	return seg;	/* 返回从队列中弹出的完整包，或者返回NULL */
}

/*********************************** 文件尾 ***********************************/
